/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 * software and associated documentation files (the "Software"), to deal in the Software 
 * without restriction, including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. 
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include "fs.h"
#include "irq.h"
#include "test.h"
#include "stdio.h"
#include "sched.h"
#include "screen.h"
#include "common.h"
#include "syscall.h"
#include "smp.h"
#include "mm.h"
#include "mac.h"
#include "string.h"
#include "../../libs/mailbox.h"

#define TASK_INIT (00)
#define STACK_MAX 0xffffffffa0f00000
#define USER_STACK 0x0000000000040000
//#define STACK_MAX 0x0000000000040000
#define STACK_SIZE 0x0000000000001000



static void init_memory()
{
}

// static void init_pcb_part1()
// {
//     uint64_t stack_top = STACK_MAX;
//     current_running = &pcb[0];
//     pcb[0].status = TASK_RUNNING;
//     queue_init(&ready_queue);
//     int i;
//     for(i = process_id; i <= num_sched1_tasks; i++){
//         pcb[i].pid = process_id++;
//         pcb[i].count = 0;
//         pcb[i].type = sched1_tasks[i - 1]->type;
//         pcb[i].status = TASK_READY;
//         pcb[i].next = NULL;
//         pcb[i].prev = NULL;

//         pcb[i].context_of_kernel.reg[31] = sched1_tasks[i - 1]->entry_point;

//         pcb[i].context_of_kernel.reg[29] = stack_top - (i - 1) * STACK_SIZE;

//         queue_push(&ready_queue, &pcb[i]);
//     }
//     // int k;
//     // for(k = 1; k <= num_lock_tasks; k++, i++){
//     //     pcb[i].pid = process_id++;
//     //     pcb[i].count = 0;
//     //     pcb[i].type = lock_tasks[k - 1]->type;
//     //     pcb[i].status = TASK_READY;
//     //     pcb[i].next = NULL;
//     //     pcb[i].prev = NULL;
//     //     pcb[i].context_of_kernel.reg[31] = lock_tasks[k - 1]->entry_point;
//     //     pcb[i].context_of_kernel.reg[29] = stack_top - (i - 1) * STACK_SIZE;
//     //     queue_push(&ready_queue, &pcb[i]);
//     // } 
// }

// static void init_pcb_part2()
// {
//     uint64_t stack_top = STACK_MAX;
//     current_running = &pcb[0];
//     pcb[0].pid = 0;
//     pcb[0].status = TASK_RUNNING;
//     queue_init(&ready_queue);
//     queue_init(&block_queue);
//     queue_init(&sleep_queue);
//     int i;
//     // for(i = process_id; i <= num_sched1_tasks; i++){
//     //     pcb[i].pid = process_id++;
//     //     pcb[i].count = 0;
//     //     pcb[i].type = sched1_tasks[i - 1]->type;
//     //     pcb[i].first_entry = 1;
//     //     pcb[i].status = TASK_READY;
//     //     pcb[i].next = NULL;
//     //     pcb[i].prev = NULL;

//     //     pcb[i].context_of_kernel.reg[31] = sched1_tasks[i - 1]->entry_point;
//     //     pcb[i].context_of_user.reg[31] = sched1_tasks[i - 1]->entry_point;

//     //     pcb[i].context_of_kernel.cp0_epc = sched1_tasks[i - 1]->entry_point;
//     //     pcb[i].context_of_user.cp0_epc = sched1_tasks[i - 1]->entry_point;

//     //     pcb[i].context_of_kernel.cp0_status = 0x10008001;
//     //     pcb[i].context_of_user.cp0_status = 0x10008001;

//     //     pcb[i].context_of_kernel.reg[29] = stack_top - (i - 1) * STACK_SIZE;
//     //     pcb[i].context_of_user.reg[29] = stack_top - (i - 1) * STACK_SIZE;

//     //     pcb[i].cursor_x = 0;
//     //     pcb[i].cursor_y = 0;

//     //     queue_push(&ready_queue, &pcb[i]);
//     // }
//     for(i = process_id; i <= num_timer_tasks; i++){
//         pcb[i].pid = process_id++;
//         pcb[i].count = 0;
//         pcb[i].type = timer_tasks[i - 1]->type;
//         pcb[i].first_entry = 1;
//         pcb[i].status = TASK_READY;
//         pcb[i].next = NULL;
//         pcb[i].prev = NULL;

//         pcb[i].context_of_kernel.reg[31] = timer_tasks[i - 1]->entry_point;
//         pcb[i].context_of_user.reg[31] = timer_tasks[i - 1]->entry_point;

//         pcb[i].context_of_kernel.cp0_epc = timer_tasks[i - 1]->entry_point;
//         pcb[i].context_of_user.cp0_epc = timer_tasks[i - 1]->entry_point;

//         pcb[i].context_of_kernel.cp0_status = 0x10008001;
//         pcb[i].context_of_user.cp0_status = 0x10008001;

//         pcb[i].context_of_kernel.reg[29] = stack_top - (i - 1) * STACK_SIZE;
//         pcb[i].context_of_user.reg[29] = stack_top - (i - 1) * STACK_SIZE;

//         pcb[i].cursor_x = 0;
//         pcb[i].cursor_y = 0;

//         queue_push(&ready_queue, &pcb[i]);
//     }
//     int k;
//     for(k = 1; k <= num_sched2_tasks; k++, i++){
//         pcb[i].pid = process_id++;
//         pcb[i].count = 0;
//         pcb[i].type = sched2_tasks[k - 1]->type;
//         pcb[i].first_entry = 1;
//         pcb[i].status = TASK_READY;
//         pcb[i].next = NULL;
//         pcb[i].prev = NULL;
//         pcb[i].context_of_kernel.reg[31] = sched2_tasks[k - 1]->entry_point;
//         pcb[i].context_of_user.reg[31] = sched2_tasks[k - 1]->entry_point;

//         pcb[i].context_of_kernel.cp0_epc = sched2_tasks[k - 1]->entry_point;
//         pcb[i].context_of_user.cp0_epc = sched2_tasks[k - 1]->entry_point;

//         pcb[i].context_of_kernel.cp0_status = 0x10008001;
//         pcb[i].context_of_user.cp0_status = 0x10008001;

//         pcb[i].context_of_kernel.reg[29] = stack_top - (i - 1) * STACK_SIZE;
//         pcb[i].context_of_user.reg[29] = stack_top - (i - 1) * STACK_SIZE;

//         pcb[i].cursor_x = 0;
//         pcb[i].cursor_y = 0;

//         queue_push(&ready_queue, &pcb[i]);
//     } 
//     int j;
//     for(j = 1; j <= num_lock_tasks; j++, i++){
//         pcb[i].pid = process_id++;
//         pcb[i].count = 0;
//         pcb[i].type = lock_tasks[j - 1]->type;
//         pcb[i].first_entry = 1;
//         pcb[i].status = TASK_READY;
//         pcb[i].next = NULL;
//         pcb[i].prev = NULL;
//         pcb[i].context_of_kernel.reg[31] = lock_tasks[j - 1]->entry_point;
//         pcb[i].context_of_user.reg[31] = lock_tasks[j - 1]->entry_point;

//         pcb[i].context_of_kernel.cp0_epc = lock_tasks[j - 1]->entry_point;
//         pcb[i].context_of_user.cp0_epc = lock_tasks[j - 1]->entry_point;

//         pcb[i].context_of_kernel.cp0_status = 0x10008001;
//         pcb[i].context_of_user.cp0_status = 0x10008001;

//         pcb[i].context_of_kernel.reg[29] = stack_top - (i - 1) * STACK_SIZE;
//         pcb[i].context_of_user.reg[29] = stack_top - (i - 1) * STACK_SIZE;

//         pcb[i].cursor_x = 0;
//         pcb[i].cursor_y = 0;

//         queue_push(&ready_queue, &pcb[i]);
//     } 
// }

static void init_pcb_part3()
{
    uint64_t stack_top = STACK_MAX;
    current_running = &pcb[0];
    pcb[0].pid = 0;
    pcb[0].status = TASK_RUNNING;
    pcb[0].context_of_kernel.reg[29] = stack_top;
    pcb_init_list[0] = 1;
    queue_init(&ready_queue);
    queue_init(&block_queue);
    queue_init(&sleep_queue);
    int i;
    for(i = 1; i < NUM_MAX_TASK; i++){
        pcb_init_list[i] = 0;
        //pcb[i].pid = -1;
        queue_init(&(pcb[i].block_queue));
        pcb[i].kernel_stack_top = stack_top - (2 * i - 1) * STACK_SIZE;
        //pcb[i].user_stack_top = stack_top - (2 * i) * STACK_SIZE;
        pcb[i].user_stack_top = USER_STACK + i * STACK_SIZE;
        //pcb[i].user_stack_top = USER_STACK;
    }
    
    pcb_init_list[1] = 1;
    pcb[1].pid = process_id++;
    pcb[1].type = USER_PROCESS;
    pcb[1].first_entry = 1;
    pcb[1].status = TASK_READY;
    pcb[1].next = NULL;
    pcb[1].prev = NULL;

    pcb[1].context_of_kernel.reg[31] = (uint64_t)return_to_user;
    //pcb[1].context_of_user.reg[31] = shell_tasks[0]->entry_point;

    pcb[1].context_of_kernel.cp0_epc = shell_tasks[0]->entry_point;
    pcb[1].context_of_user.cp0_epc = shell_tasks[0]->entry_point;

    pcb[1].context_of_kernel.cp0_status = 0x10008003;
    pcb[1].context_of_user.cp0_status = 0x10008003;

    pcb[1].context_of_kernel.reg[29] = pcb[1].kernel_stack_top;
    pcb[1].context_of_user.reg[29] = pcb[1].user_stack_top;

    queue_push(&ready_queue, &pcb[1]);
}

static void init_pcb()
{
    init_pcb_part3();
}

static void init_exception_handler()
{
    exception_handler[0] = (uint64_t)handle_int;
    int i;
    for(i = 1; i < 32; i++){
        exception_handler[i] = (uint64_t)handle_other;
    }
    exception_handler[8] = (uint64_t)handle_syscall;
    exception_handler[2] = (uint64_t)handle_tlb;
    exception_handler[3] = (uint64_t)handle_tlb;
}

static void init_exception()
{
    init_exception_handler();
    //initial_cp0_status = get_cp0_status();
    /* fill nop */
    bzero((void *)BEV0_EBASE, BEV0_OFFSET);
    memcpy((uint8_t *)(BEV0_EBASE + BEV0_OFFSET), (uint8_t *)exception_handler_entry, (uint32_t)(exception_handler_end - exception_handler_begin));
    printk("----test1----\n");
    /* fill nop */
    bzero((void *)BEV1_EBASE, BEV1_OFFSET);
    memcpy((uint8_t *)(BEV1_EBASE + BEV1_OFFSET), (uint8_t *)exception_handler_entry, (uint32_t)(exception_handler_end - exception_handler_begin));
    
    //TLB exception
    memcpy((uint8_t *)(BEV0_EBASE), (uint8_t *)TLBexception_handler_entry, (uint32_t)(TLBexception_handler_end - TLBexception_handler_begin));
    /* set COUNT & set COMPARE */
    printk("----test2----\n");
    set_cp0_count(0);
    set_cp0_compare(TIMER_INTERVAL);
    printk("----test3----\n");
    /* open interrupt */
    //set_cp0_status(0x10008001);
    printk("----test4----\n");
}

// [2]
// extern int read_shell_buff(char *buff);

static void init_syscall(void)
{
    int i;
    for(i = 0; i < NUM_SYSCALLS; i++){
        syscall[i] = (uint64_t(*)()) other_syscall;
    }
    syscall[SYSCALL_SLEEP] = (uint64_t(*)())do_sleep;
    syscall[SYSCALL_WRITE] = (uint64_t(*)())screen_write;
    syscall[SYSCALL_CURSOR] = (uint64_t(*)())screen_move_cursor;
    syscall[SYSCALL_REFLUSH] = (uint64_t(*)())screen_reflush;
    syscall[SYSCALL_MUTEX_LOCK_INIT] = (uint64_t(*)())do_mutex_lock_init;
    syscall[SYSCALL_MUTEX_LOCK_ACQUIRE] = (uint64_t(*)())do_mutex_lock_acquire;
    syscall[SYSCALL_MUTEX_LOCK_RELEASE] = (uint64_t(*)())do_mutex_lock_release;
    syscall[SYSCALL_BINSEMOP] = (uint64_t(*)())binsemop;
    syscall[SYSCALL_BINSEMGET] = (uint64_t(*)())binsemget;

    syscall[SYSCALL_SPAWN] = (uint64_t(*)())do_spawn;
    syscall[SYSCALL_KILL] = (uint64_t(*)())do_kill;
    syscall[SYSCALL_EXIT] = (uint64_t(*)())do_exit;
    syscall[SYSCALL_WAITPID] = (uint64_t(*)())do_waitpid;
    syscall[SYSCALL_CLEAR] = (uint64_t(*)())do_clear;
    syscall[SYSCALL_PS] = (uint64_t(*)())do_ps;

    syscall[SYSCALL_CONDITION_INIT] = (uint64_t(*)())do_condition_init;
    syscall[SYSCALL_CONDITION_WAIT] = (uint64_t(*)())do_condition_wait;
    syscall[SYSCALL_CONDITION_SIGNAL] = (uint64_t(*)())do_condition_signal;
    syscall[SYSCALL_CONDITION_BROADCAST] = (uint64_t(*)())do_condition_broadcast;

    syscall[SYSCALL_BARRIER_INIT] = (uint64_t(*)())do_barrier_init; 
    syscall[SYSCALL_BARRIER_WAIT] = (uint64_t(*)())do_barrier_wait; 

    syscall[SYSCALL_GETPID] = (uint64_t(*)())do_getpid;
    syscall[SYSCALL_MBOX_INIT] = (uint64_t(*)())do_mbox_init;
    syscall[SYSCALL_MBOX_OPEN] = (uint64_t(*)())do_mbox_open;
    syscall[SYSCALL_MBOX_SEND] = (uint64_t(*)())do_mbox_send;
    syscall[SYSCALL_MBOX_REC] = (uint64_t(*)())do_mbox_recv;
    syscall[SYSCALL_NET_RECV] = (uint64_t(*)())do_net_recv;
    syscall[SYSCALL_NET_SEND] = (uint64_t(*)())do_net_send;


}

/* [0] The beginning of everything >_< */
void __attribute__((section(".entry_function"))) _start(void)
{

    asm_start();

    /* init stack space */
    init_stack();
    printk("> [INIT] Stack heap initialization succeeded.\n");

    /* init interrupt */
    init_exception();
    printk("> [INIT] Interrupt processing initialization succeeded.\n");

    init_memory();
    printk("> [INIT] Virtual memory initialization succeeded.\n");
    // init system call table (0_0)
    /* init system call table */

    init_syscall();
    printk("> [INIT] System call initialized successfully.\n");

    /* init Process Control Block */

    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n");

     /* init Page */
    init_page_table_task3();
    //init_page_table();
    printk("> [INIT] Page_table initialization succeeded.\n");

    /* init TLB */
    //init_TLB();
    printk("> [INIT] TLB initialization succeeded.\n");
    /* init screen */
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n");

    do_init_mac();
    /* init filesystem */
    //read_super_block();

    /* wake up core1*/
    //loongson3_boot_secondary();

    /* set cp0_status register to allow interrupt */
    // enable exception and interrupt
    // ERL = 0, EXL = 0, IE = 1
    set_cp0_count(0);
    set_cp0_status(0x10008001);
    //printk("count = %x,compare = %x", get_cp0_count(),get_cp0_compare());

    while (1)
    {
        //do_scheduler();
        // printk("-----test0------\n");
    };
    return;
}
